package com.easepal.socketiolib;

import android.text.TextUtils;

import com.easepal.socketiolib.log.LogUtil;
import com.easepal.socketiolib.rxbus.RxBus;
import com.easepal.socketiolib.rxbus.event.ConnectActionEvent;
import com.easepal.socketiolib.rxbus.event.ConnectStatusEvent;
import com.easepal.socketiolib.rxbus.event.MessageEvent;

import java.util.LinkedList;

import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;

/**
 * 作者: bright.lin@medisana.net.cn
 * 时间: 2018/12/6
 * 标题: Socket连接
 * 描述: 通过WebSocket对象的接口调用实现连接及通信
 * 修改: 由 bright.lin@medisana.net.cn 修改于 2018/12/6
 */
class SocketConnection {
    private static final String TAG = SocketConnection.class.getName();

    // Socket对象
    private Socket socketIo;
    // Socket配置
    private IO.Options options;
    // 服务器地址
    private String url;
    // 是否已连接标志位
    private boolean isConnected = false;
    // 是否连接中标志位
    private boolean isConnecting = false;
    // 事件队列
    private LinkedList<ConnectActionEvent> events = new LinkedList<>();

    // RxBus事件总线
    private RxBus mBus;

    /**
     * 无参构造器
     */
    SocketConnection() {
        options = new IO.Options();
        options.reconnection = Constants.SocketOpt.SOCKET_RECONNECTION;
        options.reconnectionAttempts = Constants.SocketOpt.SOCKET_RECONNECTION_ATTEMPTS;
        options.reconnectionDelay = Constants.SocketOpt.SOCKET_RECONNECTION_DELAY;
        options.timeout = Constants.SocketOpt.SOCKET_CONNECTION_TIMEOUT;
        mBus = RxBus.get();
    }

    /**
     * 连接
     *
     * @param url   服务器地址
     * @param token 异步标签
     */
    void connect(String url, String token) {
        this.url = url;
        ConnectActionEvent connectActionEvent = new ConnectActionEvent(token);
        if (!isConnected) {
            if (!isConnecting) {
                doConnect(connectActionEvent);
            } else {
                connectActionEvent.onFailure(ErrorInfo.ERR_IS_CONNECTING);
                post(connectActionEvent);
            }
        } else {
            connectActionEvent.onSuccess();
            post(connectActionEvent);
        }
    }

    /**
     * 获取连接状态
     *
     * @return 连接状态
     */
    boolean isConnected() {
        return null != socketIo && socketIo.connected();
    }

    /**
     * 发送消息
     *
     * @param text 消息
     */
    void sent(String text) {
        if (null != socketIo && isConnected) {
            socketIo.send(text);
        }
    }

    void notifyReconnect() {
        handleDisconnect(true);
    }

    /**
     * 关闭
     *
     * @param notify 是否提示重连
     */
    void close(boolean notify) {
        isConnected = false;
        isConnecting = false;
        if (null != socketIo) {
            handleDisconnect(notify);
            removeSocketListener();
            socketIo.close();
            socketIo = null;
        }
    }

    /**
     * 实际连接处理
     *
     * @param connectActionEvent 连接事件
     */
    private void doConnect(ConnectActionEvent connectActionEvent) {
        if (null == socketIo) {
            try {
                socketIo = IO.socket(url, options);
                setSocketListener();
                LogUtil.d(TAG, "doConnect->socketio create url:" + url);
            } catch (Exception e) {
                connectActionEvent.onFailure(e.getMessage());
                post(connectActionEvent);
                return;
            }
        }
        addConnectEvent(connectActionEvent);
        socketIo.connect();
        LogUtil.d(TAG, "doConnect->socketio connect url:" + url);
    }

    /**
     * 设置连接回调监听
     */
    private void setSocketListener() {
        if (null != socketIo) {
            LogUtil.d(TAG, "setSocketListener");
            socketIo.on(Socket.EVENT_CONNECT, onConnect);
            socketIo.on(Socket.EVENT_CONNECTING, onConnecting);
            socketIo.on(Socket.EVENT_CONNECT_ERROR, onConnectError);
            socketIo.on(Socket.EVENT_CONNECT_TIMEOUT, onConnectTimeout);
            socketIo.on(Socket.EVENT_RECONNECT, onReconnect);
            socketIo.on(Socket.EVENT_RECONNECT_FAILED, onReconnectFailed);
            socketIo.on(Socket.EVENT_MESSAGE, onMessage);
            socketIo.on(Socket.EVENT_DISCONNECT, onDisconnect);
            socketIo.on(Constants.SOCKET_EVENT, onEvent);
            socketIo.on(Socket.EVENT_PING, onPing);
            socketIo.on(Socket.EVENT_PONG, onPong);
        }
    }

    /**
     * 移除连接回调监听
     */
    private void removeSocketListener() {
        if (null != socketIo) {
            LogUtil.d(TAG, "removeSocketListener");
            socketIo.off(Socket.EVENT_CONNECT, onConnect);
            socketIo.off(Socket.EVENT_CONNECTING, onConnecting);
            socketIo.off(Socket.EVENT_CONNECT_ERROR, onConnectError);
            socketIo.off(Socket.EVENT_CONNECT_TIMEOUT, onConnectTimeout);
            socketIo.off(Socket.EVENT_RECONNECT, onReconnect);
            socketIo.off(Socket.EVENT_RECONNECT_FAILED, onReconnectFailed);
            socketIo.off(Socket.EVENT_MESSAGE, onMessage);
            socketIo.off(Socket.EVENT_DISCONNECT, onDisconnect);
            socketIo.off(Constants.SOCKET_EVENT, onEvent);
            socketIo.off(Socket.EVENT_PING, onPing);
            socketIo.off(Socket.EVENT_PONG, onPong);
        }
    }

    // 连接成功处理
    private Emitter.Listener onConnect = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onConnect:" + getObjectFromArgs(args));
            isConnected = true;
            handleConnected();
        }
    };

    // 连接中处理
    private Emitter.Listener onConnecting = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onConnecting:" + getObjectFromArgs(args));
            isConnecting = true;
            ConnectStatusEvent connectStatusEvent = new ConnectStatusEvent();
            connectStatusEvent.setStatus(Constants.STATUS_CONNECTING);
            post(connectStatusEvent);
        }
    };

    // 连接错误处理
    private Emitter.Listener onConnectError = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onConnectError:" + getObjectFromArgs(args));
            if (!Constants.SocketOpt.SOCKET_RECONNECTION) {
                handleFailure(getMsgFromArgs(ErrorInfo.ERR_UNKNOW, args), removeConnectEvent());
                close(false);
            }
        }
    };

    // 连接超时处理
    private Emitter.Listener onConnectTimeout = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onConnectTimeout:" + getObjectFromArgs(args));
            handleFailure(ErrorInfo.ERR_CONNECT_TIMEOUT, removeConnectEvent());
            close(false);
        }
    };

    // 重连成功处理
    private Emitter.Listener onReconnect = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onReconnect:" + getObjectFromArgs(args));
            isConnected = true;
            handleConnected();
        }
    };

    // 重连失败处理
    private Emitter.Listener onReconnectFailed = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onReconnectFailed:" + getObjectFromArgs(args));
            handleFailure(getMsgFromArgs(ErrorInfo.ERR_UNKNOW, args), removeConnectEvent());
            close(false);
        }
    };

    // 消息处理
    private Emitter.Listener onMessage = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onMessage:" + getObjectFromArgs(args));
            handleMessage(args);
        }
    };

    // 断开连接处理
    private Emitter.Listener onDisconnect = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onDisconnect:" + getObjectFromArgs(args));
            close(true);
        }
    };

    // 事件处理
    private Emitter.Listener onEvent = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onEvent:" + getObjectFromArgs(args));
            handleMessage(args);
        }
    };

    // Ping处理
    private Emitter.Listener onPing = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onPing:" + getObjectFromArgs(args));
        }
    };

    // Pong处理
    private Emitter.Listener onPong = new Emitter.Listener() {

        @Override
        public void call(Object... args) {
            LogUtil.d(TAG, "onPing:" + getObjectFromArgs(args));
        }
    };

    /**
     * 处理动作成功
     *
     * @param connectActionEvent 连接事件
     */
    private void handleSuccess(ConnectActionEvent connectActionEvent) {
        if (null != connectActionEvent) {
            connectActionEvent.onSuccess();
            post(connectActionEvent);
        }
    }

    /**
     * 处理失败
     *
     * @param errorMsg           错误信息
     * @param connectActionEvent 连接事件
     */
    private void handleFailure(String errorMsg, ConnectActionEvent connectActionEvent) {
        if (null != connectActionEvent) {
            connectActionEvent.onFailure(errorMsg);
            post(connectActionEvent);
        }
    }

    /**
     * 处理连接完成
     */
    private void handleConnected() {
        if (isConnecting) {
            isConnecting = false;
            ConnectStatusEvent connectStatusEvent = new ConnectStatusEvent();
            connectStatusEvent.setStatus(Constants.STATUS_CONNECTED);
            post(connectStatusEvent);
            handleSuccess(removeConnectEvent());
        }
    }

    /**
     * 处理消息
     *
     * @param args Object对象数组
     */
    private void handleMessage(Object... args) {
        if (null != args && args.length > 0) {
            String msg = args[0].toString();
            LogUtil.d(TAG, "handleMessage:" + msg);
            if (!TextUtils.isEmpty(msg)) {
                MessageEvent messageEvent = new MessageEvent(msg);
                post(messageEvent);
            }
        }
    }

    /**
     * 处理断开连接
     *
     * @param manul 是否手动
     */
    private void handleDisconnect(boolean manul) {
        ConnectStatusEvent connectStatusEvent = new ConnectStatusEvent();
        connectStatusEvent.setStatus(Constants.STATUS_DISCONNECTED);
        connectStatusEvent.setReconnect(manul);
        post(connectStatusEvent);
    }

    /**
     * 从数组中获取对象
     *
     * @return 对象
     */
    private Object getObjectFromArgs(Object... args) {
        if (null != args && args.length > 0) {
            return args[0];
        } else {
            return null;
        }
    }

    /**
     * 从数组中获取信息
     *
     * @param defaultString 默认值
     * @return 信息
     */
    private String getMsgFromArgs(String defaultString, Object... args) {
        String msg;
        if (null != args && args.length > 0) {
            msg = args[0].toString();
        } else {
            msg = defaultString;
        }

        if (!TextUtils.isEmpty(msg)) {
            return msg;
        } else {
            return defaultString;
        }
    }

    /**
     * 添加连接事件至队尾
     *
     * @param connectActionEvent 连接事件
     */
    private void addConnectEvent(ConnectActionEvent connectActionEvent) {
        events.addLast(connectActionEvent);
    }

    /**
     * 从队头获取连接事件
     *
     * @return 连接事件
     */
    private ConnectActionEvent removeConnectEvent() {
        LogUtil.d(TAG, "removeConnectEvent:" + events.size());
        if (!events.isEmpty()) {
            return events.removeFirst();
        } else {
            return null;
        }
    }

    /**
     * RxBus发射事件
     *
     * @param o 事件对象
     */
    private void post(Object o) {
        if (null != mBus) {
            mBus.post(o);
        }
    }
}
